package chatsystem;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import views.ChatSystemView;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Random;
import protocol.*;
import views.ChatSystemConversationView;


/**
 *
 * @author Diane
 */
public class ChatSystemControler {

    private ChatSystemModel model;
    private ChatSystemView general_view;
    private ChatSystemConversationView conversation_view;
    
    private NI ni;
    private TCPNI tcpni;

    public ChatSystemControler(ChatSystemModel model, int port) {
        this.model = model;
        this.ni = new NI(this, port);
    }
    
    public void processSignal(byte[] buf) throws IOException {
        /* On désérialise le paquet reçu */
        Signal messRecu = new Signal() {};
        messRecu = messRecu.deserialize(buf);

        System.out.println("J'ai reçu un fichier: traitement du fichier");
        
        /*Si le message reçu est un file message*/
        if (messRecu instanceof FileMessage) {
            FileMessage fm = (FileMessage) messRecu;
            System.out.println("Fichier reçu : "+fm.getFileName());
            /* On ferme le socket TCP */
            tcpni.stop();

            System.out.println("reconstitution du fichier");
            try {
                // TODO Demander à l'utilisateur où il veut enregistrer le fichier et quel nom lui donner
                // TODO Que faire si le fichier n'est pas un .txt ?
                FileOutputStream fos = new FileOutputStream("C://FileIO//demo.txt");
                fos.write(fm.getFileContent());
                fos.close();
                
                /* On notifie à la vue qu'on a reçu un nouveau message texte */
                //conversation_view.displayTextMessage((TextMessage) messRecu, getUserByAddress(packet.getAddress()));
            }
            catch (Exception e) {
                System.out.println(e);
            }            
        }
    }
    
    public void TableauDeBytesVersFichier (byte[] tableau, String fic) throws java.io.IOException {
        java.io.File fichier = new File(fic);
	try {
            FileOutputStream f= new FileOutputStream(fichier);

            for (int i = 0; i < tableau.length; i++) {
                    f.write((int)tableau[i]);
        }	 
                f.close();
        }
        catch(IOException e){
                System.out.println(e+"erreur lors de la lecture du fichier");
        } 
    }

    public void processSignal (DatagramPacket packet) throws SocketException, IOException {

        /* On désérialise le paquet reçu */
        Signal messRecu = new Signal() {};
        messRecu = messRecu.deserialize(packet.getData());

        System.out.println("J'ai reçu un message: traitement du message");

        /* Si le message reçu est un Hello */
        if (messRecu instanceof Hello) {

            /* Si le Hello a besoin d'une réponse */
            if (((Hello) messRecu).isAnswerRequired() && (!packet.getAddress().equals(InetAddress.getLocalHost()))) {
                /* On crée un nouvel utilisateur dans notre système */
                User new_user = new User(((Hello)messRecu).getUserName(), packet.getAddress());
                model.addUser(new_user);
                
                /* On notifie la vue qu'un nouvel utilisateur est connecté */
                general_view.notifyHello(new_user);
                
                /* On construit le message Hello_Réponse */
                Hello hello = new Hello(model.getMySelf().getName(), false);
                this.ni.sendSignal(hello,new_user);
            }
            
            /* Si le Hello n'a pas besoin d'une réponse */
            if (!((Hello) messRecu).isAnswerRequired()) {
                /* On crée un nouvel utilisateur dans notre système */                
                User new_user = new User(((Hello)messRecu).getUserName(), packet.getAddress());
                model.addUser(new_user);
            }
        }

        /* Si le message reçu est un Bye */
        if (messRecu instanceof Bye) {
            User deco_user = null;
            System.out.println("ca vient de "+packet.getAddress());
            /* On identifie quel utilisateur s'est déconnecté */
            for (User u : model.getUsersConnected()) {
                if (u.getAddress() == packet.getAddress()) {
                    deco_user = u;
                }
            }

            if (packet.getAddress().equals(InetAddress.getLocalHost())) {
                System.out.println("ca vient de moi");
            } else {
                /* On notifie la vue qu'un nouvel utilisateur est connecté */
                general_view.notifyBye(deco_user);
                /* On supprime l'utilisateur du système */
                model.removeUser(deco_user);
            }
        }
        
        /* Si le message reçu est un NewTalk */
        if (messRecu instanceof NewTalk) {
            ArrayList<User> contributors = new ArrayList<User>();
            System.out.println("Nouvelle conversation demandée");
              // on transforme la liste de string en liste de users
            String[] contributorsIPs = ((NewTalk)messRecu).getContributorsIPs();
            for(String ip : contributorsIPs)
            {
                contributors.add(getUserByStringAddress(ip));
            }

            Conversation c = new Conversation(((NewTalk)messRecu).getTid(), contributors);
 
            model.addConversation(c);

            for (User u : contributors) {
                if (!u.getAddress().equals(InetAddress.getLocalHost())) {
                    c.addElement(u.getName());
                }
            }
            
            if (conversation_view == null) {
                conversation_view = new ChatSystemConversationView(this);
            }
            conversation_view.addNewTalk(((NewTalk)messRecu).getTid());
        }
        
        /*Si le message reçu est un text message*/
        if (messRecu instanceof TextMessage) {
            System.out.println("Message reçu : "+((TextMessage)messRecu).getMsgContent());
            /* On notifie à la vue qu'on a reçu un nouveau message texte */
            conversation_view.displayTextMessage((TextMessage) messRecu, getUserByAddress(packet.getAddress()));
        }
        
        /* Si le message reçu est un LeaveTalk*/
        if (messRecu instanceof LeaveTalk) {         
            /* On supprime l'utilisateur de la conversation */
            Conversation c = model.getConversationByID(((LeaveTalk)messRecu).getTid());
            User u = getUserByAddress(packet.getAddress());
            c.getContributors().remove(u);
            c.removeElement(u.getName());

            /* On notifie la vue de son départ */
            conversation_view.displayQuitMessage(getUserByAddress(packet.getAddress()), ((LeaveTalk)messRecu).getTid());
        }
        
        /* Si le message reçu est un AddToTalk*/
        if (messRecu instanceof AddToTalk) {
           ArrayList<User> listeInvites = new ArrayList<User>();
           System.out.println("AddToTalk reçu");
             // on transforme la liste de string en liste de users
            String[] contributorsIPs = ((AddToTalk)messRecu).getNewContributorsIPs();
            for(String ip : contributorsIPs)
            {
                //on ajoute l'invité dans la liste des contributors correspondants à la conversation
                model.getConversationByID(((AddToTalk)messRecu).getTid()).addContributor(getUserByStringAddress(ip));
                //on affiche un message dans la vue
                conversation_view.displayUserJoinMessage(getUserByStringAddress(ip),((AddToTalk)messRecu).getTid());
            }
        }
        
        /* Si le message reçu est un AskSendFile */
        if (messRecu instanceof AskSendFile) {
            User u = getUserByAddress(packet.getAddress());
            System.out.println("Demande d'envoi de fichier de "+u.getName());
            /* On notifie à la vue qu'on a reçu une demande d'envoi de fichier */
            conversation_view.displayAskSendFile((AskSendFile)messRecu, u);            
        }
        
        /* Si le message reçu est un AnswerSendFile */
        if (messRecu instanceof AnswerSendFile) {
            User u = getUserByAddress(packet.getAddress());
            AnswerSendFile asf = ((AnswerSendFile)messRecu);
            System.out.println("Réponse d'envoi de fichier de "+u.getName());
            /* Selon la réponse reçue de la part de cet utilisateur, on envoie ou pas le fichier */
            if (asf.isFileAccepted()) {                                        
                /* La demande de transfert est acceptée
                 * On se connecte au serveur pour lui envoyer le fichier 
                 */
                tcpni = new TCPNI(1235, u.getAddress(), this);
                /* Création du FileMessage à envoyer */
                File f = new File(asf.getFileName());
                FileMessage fm = new FileMessage(asf.getTid(), asf.getFileName(), fileToByte(f));
                tcpni.sendFile(fm, u);
                tcpni.stop();
            } else {
                // do nothing
            }
        }
    }
    
    static public byte[] fileToByte(File file) throws IOException { 
        InputStream in = new BufferedInputStream(new FileInputStream(file)); 
        ByteArrayOutputStream result = new ByteArrayOutputStream(); 
        BufferedOutputStream tmp = new BufferedOutputStream(result); 
        for (int b=in.read(); b != -1; b=in.read()) { 
            tmp.write(b); 
        } 
        in.close(); 
        tmp.close(); 
        return result.toByteArray(); 
    } 
    
    public void connect(String username) throws UnknownHostException, SocketException, IOException {
        model.setMySelf(new User(username, InetAddress.getLocalHost()));

        /* Connecté : j'attends de recevoir les réponses de mon Hello */
        /* Ouverture d'un thread dédié à l'écoute */
        if(ni.isSuspendu()){
            ni.restart();
        }
        else{
            ni.start();
        }

        Hello hello = new Hello(username, true);

        ni.sendSignalBroadcast(hello);

        this.model.operationEtatOnline();
        this.model.resetListUsers();
    }

    public void disconnect() throws SocketException, UnknownHostException, IOException {
        /* Création du message Bye */
        Bye bye = new Bye();

        /* Pour chaque utilisateur connecté on envoie le message Bye */
        for (User u : model.getUsersConnected()) {
            ni.sendSignal(bye, u);
        }
        
        /* On passer le système dans un état initial :
         * Toutes les informations de la session courante sont supprimées
         * C'est à dire la liste des utilisateurs, des conversations, le thread de communication est arrêté
         */
        this.model.operationEtatOffline();
        this.model.removeAllElements();
        //this.model.resetListUsers();
        this.model.setConversations(null);
        ni.pause();
    }
        
    public void createNewTalk(ArrayList<User> selectedUsers) throws SocketException, IOException {
        ArrayList<User> contributors = new ArrayList<User>();

        //creation du new talk (talkID + Ip des users désignés)
        Random randomGenerator = new Random();
        TalkID ti = new TalkID(this.model.getMySelf().getAddress().getHostAddress(),randomGenerator.nextLong());
        //on ajoute les users dans la liste d'user
        for(User u : selectedUsers){
            contributors.add(u);

        }
        contributors.add(model.getMySelf());
        // on crée une convers avec ces users
         Conversation c = new Conversation(ti, contributors);
        model.addConversation(c);

        //creation de la liste de string IP à envoyer
        String[] contributorsIPs = model.getConversationByID(ti).GetContributorsStringAddressList();


        NewTalk nt = new NewTalk(ti, contributorsIPs);

        //envoi des NewTalk
        for(User u : selectedUsers){
            System.out.println("envoi du new talk a "+u.getName()+" a l'adresse "+u.getAddress());
            /* Ajout de l'utilisateur courant à la DefaultListModel de la conversation pour affichage ultérieur */
            c.addElement(u.getName());
            ni.sendSignal(nt, u);
        }     

        if (conversation_view == null) {
            conversation_view = new ChatSystemConversationView(this);
        }

        conversation_view.addNewTalk(ti);
    }

    public void sendTextMessage(TalkID id, TextMessage msg) throws SocketException, IOException {
        /* On récupère la liste des participants de la conversation */
        ArrayList<User> contributors = model.getConversationByID(id).getContributors();
       
        /* Pour chaque participant, on envoie le message (sauf à soi-même) */
        for (User u : contributors) {   
            if (u != model.getMySelf())
            {
                System.out.println("envoi du message vers "+u.getName());
                ni.sendSignal(msg, u);
            }
        }           
    }
    
    public void sendFileMessage(TalkID id, File file) throws UnknownHostException, SocketException, IOException {
        /* Création du message AskSendFile */
        AskSendFile asf = new AskSendFile(id, file.getAbsolutePath(), file.length());

        /* On récupère la liste des participants de la conversation */
        ArrayList<User> contributors = model.getConversationByID(id).getContributors();
        
        /* Envoi du message AskSendFile à tous les participants de la conversation (sauf à soi-même) */
        for (User u : contributors) {            
            if (u != model.getMySelf()) {                
                System.out.println("envoi de la demande de fichier vers "+u.getName()+" à l'adresse "+u.getAddress());
                ni.sendSignal(asf, u);
            }
        }                
    }

    public void acceptFile(AskSendFile asf, User u) throws SocketException, IOException {
        /* Création du AnswerSendFile */
        AnswerSendFile answer = new AnswerSendFile(asf.getTid(), asf.getFileName(), true);
        /* On crée un socket TCP server pour récupérer le fichier */
        tcpni = new TCPNI(1235, this);
        /* Envoi du message AnswerSendFile à l'initiateur de la demande */
        ni.sendSignal(answer, u);
    }

    public void declineFile(AskSendFile asf, User u) throws SocketException, IOException {
        /* Création du AnswerSendFile */
        AnswerSendFile answer = new AnswerSendFile(asf.getTid(), asf.getFileName(), false);
        /* Envoi du message AnswerSendFile à l'initiateur de la demande */
        ni.sendSignal(answer, u);
    }
    
    public void quitConversation(TalkID tid) throws UnknownHostException, SocketException, IOException
    {
        System.out.println("on quitte la convers");
        ArrayList<User> contributors = model.getConversationByID(tid).getContributors();
        System.out.println("ID DE LA CONVERS A QUITER "+model.getConversationByID(tid).getTid());
        model.getConversations().remove(model.getConversationByID(tid));
        
        /* Création du message LeaveTalk */
        LeaveTalk lv = new LeaveTalk(tid);
        
        /* Pour chaque participant on notifie qu'on quitte la conversation */
        for (User u : contributors) {
            if (u != model.getMySelf())
            {
                System.out.println("envoi du message vers "+u.getName());
                ni.sendSignal(lv,u);
            }            
        }        
    }

    public void invitUser(ArrayList<User> selectedUsers, TalkID ti) throws SocketException, IOException {
        //liste des addr users invités
        String[] invitsIPs = new String[selectedUsers.size()];
        //liste des participants (sans les users invités)
        ArrayList<User> contributorsDejaPresents = model.getConversationByID(ti).getContributors();

        //on rajoute les invites a la convers
        for(User u : selectedUsers){
            model.getConversationByID(ti).addContributor(u);
        }
   
        //creation du newtalk pour les invités
        NewTalk nt = new NewTalk(ti, model.getConversationByID(ti).GetContributorsStringAddressList());

        //envoi des NewTalk aux invités
        for(User u : selectedUsers){
            System.out.println("envoi du new talk a "+u.getName()+" a l'adresse "+u.getAddress());
            ni.sendSignal(nt, u);
        }
        
        //creation de la liste string des users invités
        int i = 0;
          for(User u : selectedUsers){
            invitsIPs[i]= u.getAddress().getHostAddress();
               i++;
          }
        //creation du addtotalk  
        AddToTalk att = new AddToTalk(ti,invitsIPs);

        //envoi des addtotalk aux contribs presents
          for(User u : contributorsDejaPresents){
               if (u != model.getMySelf())
               {
                    System.out.println("envoi du addtotalk a "+u.getName());
                    ni.sendSignal(att, u);
                }
            }
    }

    public User getUserByName(String username) {
        User user = null;
        for (User u : model.getUsersConnected()) {
            if (u.getName().equals(username)) {
                user = u;
            }
        }
        return user;
    }

    public User getUserByAddress(InetAddress ip) {
        User user = null;
        for (User u : model.getUsersConnected()) {
            if (u.getAddress().equals(ip)) {
                user = u;
            }
        }
        return user;
    }
    public User getUserByStringAddress(String ip) throws UnknownHostException {
        User user = null;
        //on teste si c'est nous
        if (model.getMySelf().getAddress().getHostAddress().equals(ip)) {
                user= model.getMySelf();
            }
        else {
        for (User u : model.getUsersConnected()) {
            if (u.getAddress().getHostAddress().equals(ip)) {
                user = u;
            }
        }
        }
        return user;
    }

    public Conversation getConversationByID(TalkID id) {
        Conversation convers = null;
        for (Conversation c : model.getConversations()) {
            if (c.getTid().equals(id)) convers = c;
        }
        return convers;
    }
    

    public ChatSystemView getGeneral_view() {
        return general_view;
    }

    public void setGeneral_view(ChatSystemView current_view) {
        this.general_view = current_view;
    }

    public ChatSystemConversationView getConversation_view() {
        return conversation_view;
    }

    public void setConversation_view(ChatSystemConversationView current_view) {
        this.conversation_view = current_view;
    }

    /**
     * @return the model
     */
    public ChatSystemModel getModel() {
        return model;
    }

    /**
     * @param model the model to set
     */
    public void setModel(ChatSystemModel model) {
        this.model = model;
    }
}

